/**
 *
 * 212所报告
 *
 * Created by yangyunze on 2024/06/04
 */

const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const { Types } = require('mongoose');
const { VARIABLE_KIND, VARIABLE_SCOPE_KIND } = require('smart-rocket-unit');

const Docx = require('./../adapter/docx');
const DefaultReporter = require('./../default-report');
const ProjectVersionSpliter = require('../../project-version-spliter');
const fileElements = require('./../../../file-elements');
const tests = require('./../../../tests');
const utility = require('./../../../utility');
const { type: taskType } = require('./../../../tasks');
const { kind: fileKind } = require('./../../../files');
const Task = require('./../../../mongo/task');
const File = require('./../../../mongo/file');
const functionModels = require('../../../functions');
const classCreator = require('./../base-report-test-case-transformer');
const logger = require('../../../logger');

class Reporter extends DefaultReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.baseDir = __dirname;
    this.reportTime = moment().format('YYYY年MM月');
    this.funcTestResultStatistic = {};
    this.enabledCoverageKind = [];
    this.ReportTestCaseTransformer = classCreator('unitTest');
    this.fileMap = new Map();
    // 报告无法识别的异常字符
    // eslint-disable-next-line no-control-regex
    this.unusualCharReg = /[\x00-\x08\x0B\x0C\x0E-\x1F]/g;
  }

  // 重写报告格式化对象的getAreas方法，设置要组装的报告数据
  overridingGetAreas() {
    this.ReportTestCaseTransformer.prototype.getAreas = function getAreas() {
      if (this.stubsData && this.stubsData.length > 0) {
        this.stubsData.forEach((stub) => {
          if (stub.name && stub.typeName) {
            stub.typeName = `函数: ${stub.typeName}`;
          }
        });
      }
      return [this.stubsData];
    };
  }

  async generate(progressIndicator) {
    const fileName = '软件单元测试报告';
    const result = {
      cachePath: this.outputDir,
    };
    this.overridingGetAreas();
    const toolInfo = require('../../../../../package.json');
    const toolVersion = toolInfo.version;
    ['statement', 'branch', 'condition', 'decision', 'mcdc'].forEach((key) => {
      if (this.version.coverages & tests.coverageKind[key]) {
        const o = {
          key,
          coverageKey: `${key}Coverage`,
          totalKey: `${key}Total`,
          coveredKey: `${key}Covered`,
          name: this.i18n.__(`tests.${key}Coverage`),
        };
        this.enabledCoverageKind.push(o);
      }
    });
    const { versionPath, projectDir } = await this.spliteByProjectVersion();
    this.versionDir = versionPath;
    this.projectDir = projectDir;
    const reportPath = `${path.join(this.versionDir, fileName)}`;
    const [files, tasks] = await Promise.all([
      this.spliteByFile(progressIndicator),
      Task.find({ versionId: this.version._id }, ['type']),
    ]);
    const [functionsOverview] = await this.getDataAdapter().getFunctionsOverview(this.versionId, { pageSize: 999999 });
    let testCountTotal = 0;
    functionsOverview.forEach((overview, index) => {
      overview.index = index + 1;
      overview.coverageInfo = [];
      this.enabledCoverageKind.forEach((coverageKind) => {
        let coverage = '-';
        if (overview[coverageKind.totalKey]) {
          coverage = overview[coverageKind.coveredKey]
            / overview[coverageKind.totalKey];
          coverage = `${Math.round(coverage * 10000) / 100}%`;
        }
        const o = {
          coverage,
        };
        overview.coverageInfo.push(o);
      });
      testCountTotal += overview.testCount;
      overview.passedCount = 0;
      overview.failedCount = overview.testCount;
      if (this.funcTestResultStatistic[overview._id.toString()]) {
        overview.passedCount = this.funcTestResultStatistic[overview._id.toString()].passedCount;
        overview.failedCount = overview.testCount - overview.passedCount;
      }
      overview.commentRate = overview.numberOfLines ? Math.round((overview.commentLines / overview.numberOfLines) * 10000) / 100 : 0;
    });
    await this.generateFileReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        toolVersion,
        enabledCoverageKind: this.enabledCoverageKind,
        language: this.version.language,
        funcsOverview: functionsOverview,
        files: files.filter((file) => file.functions.length > 0),
        reportTime: this.reportTime,
        reportDate: moment().format('YYYY年MM月DD日'),
        tester: this.creator.username,
        compiler: this.version['project type'],
        testCountTotal,
        tasks: tasks.map((task) => {
          let name = '';
          switch (task.type) {
            case taskType.parse:
              name = '源码解析';
              break;
            case taskType.testCaseGenerate:
              name = '生成用例';
              break;
            case taskType.testCaseRun:
              name = '执行用例';
              break;
            case taskType.autoTest:
              name = '一键测试';
              break;
            case taskType.autoRegression:
              name = '一键回归';
              break;
            case taskType.autoTestAndFilter:
              name = '一键补充测试';
              break;
            default:
              return null;
          }
          return {
            name,
            user: this.creator.username,
            date: moment(Types.ObjectId(task._id).getTimestamp()).format('YYYY-MM-DD HH:mm:ss'),
          };
        }).filter((task) => task),
      },
      reportPath
    );
    result.reportPath = path.join(this.outputDir, `${fileName}.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  async spliteByFile(progressIndicator) {
    await fs.ensureDir(this.versionDir);
    const sourceFileExts = [];
    const languages = { ...this.version.languages, headers: this.version.headers };
    Object.keys(languages).forEach((key) => {
      if (languages[key].suffixes) {
        sourceFileExts.push(...languages[key].suffixes);
      }
    });
    const filter = {
      versionId: this.versionId,
      $or: [
        { kind: fileKind.file },
      ],
      $and: [{ fileName: { $nin: ['.gitignore', '.smartrocket'] } }],
    };
    if (sourceFileExts && sourceFileExts.length > 0) {
      filter.$or.push({
        exname: { $in: sourceFileExts },
      });
    }
    const allFiles = await File.find(filter, ['_id', 'fileName', 'path']).lean();
    allFiles.forEach((file) => {
      this.fileMap.set(file._id.toString(), file.fileName);
    });
    return this.handleFiles(allFiles, this.versionDir, progressIndicator);
  }

  async handleFiles(files, versionDir, progressIndicator) {
    return utility.arrayChunkOperation(files, 1, async (file) => {
      const result = await this.handleFile(file, versionDir);
      if (progressIndicator) {
        progressIndicator.advance(1 / files.length);
      }
      return result;
    });
  }

  async handleFile(file, versionDir) {
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const result = {
      fileName: file.fileName,
      filePath: file.path,
      summary: {},
      functions: [],
    };
    res.functions = res.functions.sort((a, b) => (a.index > b.index ? 1 : -1));
    result.summary = DefaultReporter.getSummaryOfFile(res.functions);
    res.functions.forEach((func) => {
      if (!fileElements.isTestableElement(func.kind) || func.tests.length === 0) {
        return;
      }
      let passedCount = 0;
      const newFunc = {
        functionName: func.functionName,
        enabledCoverageKindCount: this.enabledCoverageKind.length,
        summary: this.getSummaryOfFunction(func),
        tests: [],
      };
      const functionVariables = functionModels.getFunctionVariableJson(func.variables);
      functionVariables.functionName = func.functionName;
      let testIndex = 1;
      func.tests.forEach((test) => {
        if (![
          tests.resultKind.succeed,
          tests.resultKind.noResult,
          tests.resultKind.nonStrictSucceed,
          tests.resultKind.failed].includes(test.resultKind)) {
          return;
        }
        if (test.resultKind !== tests.resultKind.failed) {
          passedCount += 1;
        }
        test = this.getTestDetail(res.typeSystem, functionVariables, test);
        if (!test) {
          return;
        }
        test.index = '0'.repeat(4 - testIndex.toString().length) + testIndex.toString();
        testIndex += 1;
        newFunc.tests.push(test);
      });
      if (newFunc.tests.length === 0) {
        return;
      }
      this.funcTestResultStatistic[func._id.toString()] = { passedCount };
      result.functions.push(newFunc);
    });
    return result;
  }

  /**
   * 变量展开平铺
   * @param {Object} variable 变量数据
   * @param {Array[String]} resVariables 平铺好的变量
   * @param {Number} scope 输入输出域
   * @param {Number} resultKind 测试用例执行状态
   */
  variableRecursion(variable, resVariables = [], scope, resultKind) {
    if (variable.children) {
      variable.children.forEach((childVariable) => {
        if (variable.baseVariableName) {
          childVariable.baseVariableName = variable.baseVariableName;
        }
        this.variableRecursion(childVariable, resVariables, scope, resultKind);
      });
    } else {
      let newVariableName = variable.fieldPath.replace(/output\.|variables\.|params\.|malloc\.|global\.|statics\.|\.@value|\.@init|\.@ctor/g, '');
      // 局部静态变量需要特殊处理
      if (variable.kind === VARIABLE_KIND.statics) {
        newVariableName = newVariableName.replace(/^[\d]+|^\d+$/, `${variable.baseVariableName}`);
      }
      // 处理数组元素
      const arryIndexReg = /\.[\d]+/g;
      if (newVariableName.match(arryIndexReg)) {
        const arrayIndexs = newVariableName.match(arryIndexReg);
        arrayIndexs.forEach((arryIndexStr) => {
          newVariableName = newVariableName.replace(arryIndexStr, `[${arryIndexStr.split('.')[1]}]`);
        });
      }
      // 处理简单返回值
      if (newVariableName === '%') {
        newVariableName = 'return';
      }
      // 处理复杂结构返回值
      if (newVariableName.match(/%\./)) {
        newVariableName = newVariableName.replace(/%\./, 'return ');
      }
      newVariableName = newVariableName.replace(this.unusualCharReg, '');
      const value = variable.data && variable.data['@value'] ? variable.data['@value'] : '';
      let result = variable.data && variable.data['@result'] ? variable.data['@result'] : '';
      // 报告不显示没有填值的变量
      if (!this.complete && !value) {
        return;
      }
      if (scope === VARIABLE_SCOPE_KIND.output && [
        tests.resultKind.succeed,
        tests.resultKind.noResult,
        tests.resultKind.nonStrictSucceed,
        tests.resultKind.failed,
      ].includes(resultKind) && value && !result) {
        result = value;
      }
      resVariables.push({
        variableName: newVariableName,
        typeName: variable.typeName,
        value,
        result,
      });
    }
  }

  getTestDetail(typeSystem, functionVariables, test) {
    const newTestcase = tests.getJson(test, this.i18n);
    const transformer = new this.ReportTestCaseTransformer(
      newTestcase,
      functionVariables,
      typeSystem,
      this.i18n
    );
    try {
      transformer.transform(this.complete);
    } catch (error) {
      logger.error(`testCase transform error: fileName: ${this.fileMap.get(functionVariables.fileId.toString())} function name: ${functionVariables.functionName} test case number: ${test.number}:`, error);
      return null;
    }
    const newInputDetails = {
      variables: [],
      stubs: [],
    };
    // output顺序 函数返回值，全局变量，局部静态变量，指针目标
    const newOutputDetails = [];
    // 处理桩函数打桩情况
    transformer.input.forEach((input) => {
      // 处理异常格式的字符，防止导入报告失败
      input.variable = input.variable.replace(this.unusualCharReg, '');
      // 处理变量值显示格式
      if (input.value) {
        input.value = ` = ${input.value}`;
      }
      let bold = false;
      if ((input.category && !input.variable && !input.value) || (input.variable && input.variable.match(/第[\d]+次调用|函数:/))) {
        bold = true;
      }
      if (input.category && input.variable) {
        newInputDetails.stubs.push({
          category: `${input.category}:`,
          value: '',
          variable: '',
          bold: true,
        });
        newInputDetails.stubs.push({
          category: '',
          value: input.value,
          variable: input.variable,
          bold,
        });
      } else {
        newInputDetails.stubs.push({ ...input, bold });
      }
    });
    // 处理输入
    [
      ...transformer.paramVariablesData,
      ...transformer.globalVariablesData,
    ].forEach((variable) => {
      // 局部静态变量需要特殊处理
      if (variable.kind === VARIABLE_KIND.statics) {
        variable.baseVariableName = variable.name;
      }
      this.variableRecursion(variable, newInputDetails.variables, VARIABLE_SCOPE_KIND.input, test.resultKind);
    });
    // 处理输出
    [
      ...transformer.returnValueData,
      ...transformer.globalOutputData,
      ...transformer.staticsOutputData,
      ...transformer.mallocOutputData].forEach((variable) => {
      // 局部静态变量需要特殊处理
      if (variable.kind === VARIABLE_KIND.statics) {
        variable.baseVariableName = variable.name;
      }
      this.variableRecursion(variable, newOutputDetails, VARIABLE_SCOPE_KIND.output, test.resultKind);
    });
    return {
      prelude: transformer.prelude || [{ line: '无' }],
      input: newInputDetails,
      output: newOutputDetails,
    };
  }

  generateFileReport(data, output) {
    const template = 'file-template';
    data.reportFooterText = this.reportFooterText;
    return (async () => {
      output = `${output}.docx`;
      const tpl = new Docx(path.join(this.baseDir, 'template', 'docx', `${template}.docx`));
      await tpl.generate(
        data,
        output,
      );
      return output;
    })();
  }
}

module.exports = Reporter;
